home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Celestin Apprentice 5
/
Apprentice-Release5.iso
/
Source Code
/
C
/
Snippets
/
Stuart's Tech Notes
/
StUU
/
Sources
/
DSUserProcs.c
< prev
next >
Wrap
Text File
|
1996-04-02
|
41KB
|
1,248 lines
/******************************************************************************
**
** Project Name: DropShell
** File Name: DSUserProcs.c
**
** Description: Specific AppleEvent handlers used by the DropBox
**
*******************************************************************************
** A U T H O R I D E N T I T Y
*******************************************************************************
**
** Initials Name
** -------- -----------------------------------------------
** LDR Leonard Rosenthol
** MTC Marshall Clow
** SCS Stephan Somogyi
**
*******************************************************************************
** R E V I S I O N H I S T O R Y
*******************************************************************************
**
** Date Time Author Description
** -------- ----- ------ ---------------------------------------------
** 06/23/94 LDR Added support for ProcessItem and ProcessFolder handling
** 02/20/94 LDR Modified Preflight & Postflight to take item count
** 01/25/92 LDR Removed the use of const on the userDataHandle
** 12/09/91 LDR Added the new SelectFile userProc
** Added the new Install & DisposeUserGlobals procs
** Modified PostFlight to only autoquit on odoc, not pdoc
** 11/24/91 LDR Added the userProcs for pdoc handler
** Cleaned up the placement of braces
** Added the passing of a userDataHandle
** 10/29/91 SCS Changes for THINK C 5
** 10/28/91 LDR Officially renamed DropShell (from QuickShell)
** Added a bunch of comments for clarification
** 10/06/91 00:02 MTC Converted to MPW C
** 04/09/91 00:02 LDR Added to Projector
**
******************************************************************************/
#include <StandardFile.h>
#include <Timer.h> // For timing
#include <ICTypes.h>
#include <ICKeys.h>
#include <ICAPI.h>
#include "DSGlobals.h"
#include "DSUserProcs.h"
#ifdef __MC68K__
#include <StuTypes.h>
#define WideAdd(A,B)
#define WideSubtract(A,B)
#endif
// Static Prototypes
static OSErr ProcessItem(FSSpecPtr myFSSPtr);
static OSErr ProcessFolder(FSSpecPtr myFSSPtr);
/*
Uncomment this line if you want each item of a dropped folder processed
as an individual item
*/
#define qWalkFolders
/*
This routine is called during init time.
It allows you to install more AEVT Handlers beyond the standard four
*/
#pragma segment Main
pascal void InstallOtherEvents(void) {
}
/*
This routine is called when an OAPP event is received.
Currently, all it does is set the gOApped flag, so you know that
you were called initally with no docs, and therefore you shouldn't
quit when done processing any following odocs.
*/
#pragma segment Main
pascal void OpenApp(void) {
gOApped = true;
}
/*
This routine is called when an QUIT event is received.
We simply set the global done flag so that the main event loop can
gracefully exit. We DO NOT call ExitToShell for two reasons:
1) It is a pretty ugly thing to do, but more importantly
2) The Apple event manager will get REAL upset!
*/
#pragma segment Main
pascal void QuitApp(void) {
gDone = true; /* All Done! */
}
/*
This routine is the first one called when an ODOC or PDOC event is received.
In this routine you would place code used to setup structures, etc.
which would be used in a 'for all docs' situation (like "Archive all
dropped files")
Obviously, the opening boolean tells you whether you should be opening
or printing these files based on the type of event recieved.
NEW IN 2.0!
The itemCount parameter is simply the number of items that were dropped on
the application and that you will be processing. This gives you the ability
to do a single preflight for memory allocation needs, rather than doing it
once for each item as in previous versions.
userDataHandle is a handle that you can create & use to store your own
data structs. This dataHandle will be passed around to the other
odoc/pdoc routines so that you can get at your data without using
globals - just like the new StandardFile.
We also return a boolean to tell the caller if you support this type
of event. By default, our dropboxes don't support the pdoc, so when
opening is FALSE, we return FALSE to let the caller send back the
proper error code to the AEManager.
You will probably want to remove the #pragma unused (currently there to fool the compiler!)
*/
#define DO_TIMING 0
#define IsError(X) ((X) !=1 && (X) != 0)
#define Alert_NoMemory 130
#define Alert_NoIC 131
#define Alert_DiskError 132
#if DO_TIMING
static UnsignedWide total_start_time, total_end_time;
static UnsignedWide create_start_time, create_end_time, create_time;
static UnsignedWide write_start_time, write_end_time, write_time;
static UnsignedWide close_start_time, close_end_time, close_time;
static UnsignedWide read_start_time, read_end_time, read_time;
#endif
typedef struct { HParamBlockRec io; void *GlobalsReg; } ex_HParamBlockRec;
static Handle InBuffer; // Handle to input buffer
static Handle OutBuffer1, OutBuffer2; // Handles to output output buffers
static Handle OutBuffer; // Handle to current output buffer
static long InHandleSize; // Size of input buffer
static long OutHandleSize; // Size of output buffer
static short ReadRefNum; // The file being read
static long BytesRemaining; // Bytes of file still unread
static Ptr InPtr = NULL; // Next character to read from buffer
static Ptr InEnd = NULL; // End of data in buffer
static char *CurrentLine; // Pointer to current line in the inbuffer
static int LineLength; // Length of current line
static char TempLineBuffer[256]; // Temp holding buffer for lines spanning buffers
static Ptr OutPtr; // Next byte to written to buffer
static Ptr OutEnd; // End output buffer
static WindowPtr StatusWindow;
static ICInstance IC_instance = NULL;
static Handle IC_mappings = NULL;
static ex_HParamBlockRec Async_Create_PB;
static HParamBlockRec Async_Info_PB, Async_Open_PB;
static ParamBlockRec Async_Write_PB, Async_Close_PB;
static volatile Boolean FileBeingCreated;
static FSSpec outputSpec, writeSpec, closeSpec;
static unsigned char output_namesuffix;
static unsigned char output_namesuffix_length;
static OSType output_type, output_creator;
static const Point zeroPoint;
static void MakeStatusWindow(void)
{
Rect bounds;
short screencentre = (qd.screenBits.bounds.right - qd.screenBits.bounds.left) / 2;
bounds.top = 100;
bounds.bottom = 290;
bounds.left = screencentre - 200;
bounds.right = screencentre + 200;
StatusWindow = NewWindow(NULL, &bounds, NULL, FALSE, dBoxProc, (WindowPtr)-1, FALSE, 0);
}
static void PaintStatusWindow(void)
{
SetPort(StatusWindow);
TextSize(12);
TextFace(bold);
MoveTo(20, 30); DrawString("\pFast, Simple, Stupid UUDecoder");
MoveTo(20, 50); DrawString("\pby Stuart Cheshire <cheshire@cs.stanford.edu>");
MoveTo(20, 70); DrawString("\pThis software is free.");
MoveTo(20, 90); DrawString("\pYou can do whatever you like with it.");
TextFace(0);
MoveTo(20,110); DrawString("\pBuilt using DropShell 2.0");
MoveTo(20,130); DrawString("\pby Leonard Rosenthol, Marshall Clow, and Stephan Somogyi");
}
static void UpdateStatusWindowInput(unsigned char *inputname)
{
Rect r = StatusWindow->portRect;
r.top = 135;
r.bottom = 155;
r.left = 20;
EraseRect(&r);
TextFace(bold);
MoveTo(20,150); DrawString("\pReading File: "); DrawString(inputname);
}
static void UpdateStatusWindowOutput(unsigned char *outputname)
{
Rect r = StatusWindow->portRect;
r.top = 155;
r.left = 20;
EraseRect(&r);
TextFace(bold);
MoveTo(20,170); DrawString("\pWriting File: "); DrawString(outputname);
}
static void UpdateStatusWindow(Boolean reading)
{
Rect r = StatusWindow->portRect;
r.top = 135;
r.right = 20;
EraseRect(&r);
if (reading) MoveTo(8,150); else MoveTo(8,170);
TextFace(0);
DrawString("\p•");
}
static void StUU_Exit(void)
{
if (InBuffer ) { DisposeHandle(InBuffer ); InBuffer = NULL; }
if (OutBuffer1) { DisposeHandle(OutBuffer1); OutBuffer1 = NULL; }
if (OutBuffer2) { DisposeHandle(OutBuffer2); OutBuffer2 = NULL; }
if (StatusWindow) { DisposeWindow(StatusWindow); StatusWindow = NULL; }
if (IC_instance) { ICStop(IC_instance); IC_instance = NULL; }
}
static Boolean TrapAvailable(unsigned long trap)
{
TrapType tType = (trap & 0x800 ? ToolTrap : OSTrap);
if (trap & 0x800) // if it is a ToolBox Trap
{
unsigned long n = 0x400; // number of toolbox traps
if (NGetTrapAddress(_InitGraf, ToolTrap) == NGetTrapAddress(0xAA6E, ToolTrap))
n = 0x200;
if ((trap &= 0x7FF) >= n) trap = _Unimplemented;
}
return(NGetTrapAddress(trap, tType) != NGetTrapAddress(_Unimplemented, ToolTrap));
}
#ifdef __MC68K__
// The definition of GetHoldableBytes for 68K code
#if !GENERATINGCFM
#pragma parameter __D0 GetHoldableBytes()
#endif
extern pascal Size GetHoldableBytes(void) TWOWORDINLINE(0x70FD, 0xA05C);
#else
// The definition of GetHoldableBytes for PPC code
static pascal Size GetHoldableBytes(void)
{
static short GetHoldableBytes68K[] =
{
0x70FD, // moveq #-3,d0
0xA05C, // MemoryDispatch
0x4E75, // rts
};
enum
{
uppGetHoldableBytesProcInfo = kRegisterBased
| RESULT_SIZE(kFourByteCode) | REGISTER_RESULT_LOCATION(kRegisterD0)
};
return(CallUniversalProc((UniversalProcPtr)GetHoldableBytes68K, uppGetHoldableBytesProcInfo));
}
#endif
static Size sensibleTempMaxMem(Size *grow)
{
Size freemem = TempMaxMem(grow);
long VMAttr, PhysicalRAM, LogicalRAM;
// if VM on, see if we can revise our estimate of free memory to a more sensible value
if (TrapAvailable(_Gestalt) &&
Gestalt(gestaltVMAttr, &VMAttr) == noErr && (VMAttr & (1 << gestaltVMPresent)) &&
Gestalt(gestaltPhysicalRAMSize, &PhysicalRAM) == noErr &&
Gestalt(gestaltLogicalRAMSize, &LogicalRAM) == noErr)
{
// See how much physical RAM is available
// If MemoryDispatch is available, use it, else just use
// a simplistic estimate that half of the physical RAM is holdable
Size holdable = TrapAvailable(_MemoryDispatch) ? GetHoldableBytes() : PhysicalRAM / 2;
// See how much memory is in use
Size usedmem = LogicalRAM - TempFreeMem();
// See how much physical RAM is free
Size useable = 0;
if (holdable > usedmem) useable = holdable - usedmem;
// If there is very little (or no) physical RAM left, then we'll
// steal 1/16 of the holdable pages (e.g. 1M on a 16M machine)
if (useable < holdable/16) useable = holdable/16;
// If our initial freemem value is more than this amount
// that we deem sensible to allocate, reduce it
if (freemem > useable) freemem = useable;
}
return(freemem);
}
#pragma segment Main
pascal Boolean PreFlightDocs (Boolean opening, short itemCount, Handle *userDataHandle) {
#pragma unused ( itemCount )
#pragma unused ( userDataHandle )
OSErr resultCode1 = noErr, resultCode2 = noErr;
ICError ICerr;
ICAttr attr;
Size maxTM, maxHM, growTM = 0, growHM = 0;
if (!opening) return(FALSE); // we support opening, but not printing - see above
MakeStatusWindow();
ICerr = ICStart(&IC_instance, 'StUU');
if (!ICerr) ICerr = ICFindConfigFile(IC_instance, 0, nil);
if (!ICerr) ICerr = ICGetPrefHandle(IC_instance, kICMapping, &attr, &IC_mappings);
if (ICerr) { (void)Alert(Alert_NoIC, NULL); IC_mappings = NULL; }
// See how much free memory we have in the temporary pool, and in the application heap
maxTM = sensibleTempMaxMem(&growTM) & ~0xFFF;
maxHM = MaxMem(&growHM) & ~0xFFF;
// Make sure we keep 32K of application memory in reserve for alerts etc.
if (maxHM < 0x8000) maxHM = 0; else maxHM -= 0x8000;
// Note: We lock all the handles as soon as possible, otherwise the System has
// a habit of trying to copy them around to compact the heap, and that absolutely
// kills performance. If we don't lock the handles, on a machine with VM the
// launch time can be 10-20 seconds, even using the sensibleTempMaxMem routine.
if (maxTM > maxHM)
{
InHandleSize = maxTM >> 1;
OutHandleSize = InHandleSize >> 1;
while (InHandleSize > 0x1000)
{
InBuffer = TempNewHandle(InHandleSize, &resultCode1);
if (resultCode1) { InHandleSize >>= 1; continue; }
HLock(InBuffer);
break;
}
while (OutHandleSize > 0x1000)
{
OutBuffer1 = TempNewHandle(OutHandleSize, &resultCode2);
if (resultCode2) { OutHandleSize >>= 1; continue; }
HLock(OutBuffer1);
OutBuffer2 = TempNewHandle(OutHandleSize, &resultCode2);
if (resultCode2) { DisposeHandle(OutBuffer1); OutBuffer1 = NULL; OutHandleSize >>= 1; continue; }
HLock(OutBuffer2);
break;
}
}
else
{
InHandleSize = maxHM >> 1;
OutHandleSize = InHandleSize >> 1;
while (InHandleSize > 0x1000)
{
InBuffer = NewHandle(InHandleSize);
if (!InBuffer) { InHandleSize >>= 1; continue; }
HLock(InBuffer);
break;
}
while (OutHandleSize > 0x1000)
{
OutBuffer1 = NewHandle(OutHandleSize);
if (!OutBuffer1) { InHandleSize >>= 1; continue; }
HLock(OutBuffer1);
OutBuffer2 = NewHandle(OutHandleSize);
if (!OutBuffer2) { DisposeHandle(OutBuffer1); OutBuffer1 = NULL; OutHandleSize >>= 1; continue; }
HLock(OutBuffer2);
break;
}
resultCode1 = (InBuffer == NULL);
resultCode2 = (OutBuffer2 == NULL);
}
if (resultCode1 || resultCode2) { (void)Alert(Alert_NoMemory, NULL); StUU_Exit(); return(FALSE); }
OutBuffer = OutBuffer1;
ShowWindow(StatusWindow);
PaintStatusWindow();
#if DO_TIMING
Microseconds(&total_start_time);
create_time.lo = create_time.hi = 0;
write_time .lo = write_time .hi = 0;
close_time .lo = close_time .hi = 0;
read_time .lo = read_time .hi = 0;
#endif
return(TRUE);
}
/*
This routine is called for each file passed in the ODOC event.
In this routine you would place code for processing each file/folder/disk that
was dropped on top of you.
You will probably want to remove the #pragma unused (currently there to fool the compiler!)
*/
#pragma segment Main
pascal void OpenDoc ( FSSpecPtr myFSSPtr, Boolean opening, Handle userDataHandle ) {
#pragma unused ( myFSSPtr )
#pragma unused ( opening )
#pragma unused ( userDataHandle )
OSErr err = noErr;
#ifdef qWalkFolders
/*
For this case we need to determine if the FSSpec is a file or folder.
If it's a folder, we then need to process each item in that folder,
otherwise just process the item.
*/
if (FSpIsFolder(myFSSPtr))
err = ProcessFolder(myFSSPtr);
else
err = ProcessItem(myFSSPtr);
#else
/*
For this case we just call ProcessItem on the FSSpec above.
*/
err = ProcessItem(myFSSPtr);
#endif
// you should probably do something if you get back an error ;)
}
// CopyData Benchmarks
// 82 182 number of bytes (copied 100,000 times)
// --- ---
// 145 212 BlockMove
// 145 212 BlockMoveData
// 134 283 CopyData
// 113 237 CopyData in assembler
// Conclusion: For small blocks, a subroutine is quicker than the BlockMove trap
static void CopyData(register Ptr src, register Ptr dest, register unsigned short len)
{
#if THINK_C
asm {
sub.w #1, len
@0 move.b (src)+, (dest)+
dbra len, @0
}
#else
while(len) { *dest++ = *src++; len--; }
#endif
}
static void ShowMsg(unsigned char *msg, unsigned long time)
{
unsigned char buffer[128], number[16];
NumToString(time, number);
CopyData((Ptr)msg + 1, (Ptr)buffer + 1, msg[0]);
CopyData((Ptr)number + 1, (Ptr)buffer + 1 + msg[0], number[0]);
buffer[0] = msg[0] + number[0];
DebugStr(buffer);
}
static void ShowWindowMsg(short y, unsigned char *msg, unsigned long time)
{
unsigned char buffer[128], number[16];
NumToString(time, number);
CopyData((Ptr)msg + 1, (Ptr)buffer + 1, msg[0]);
CopyData((Ptr)number + 1, (Ptr)buffer + 1 + msg[0], number[0]);
buffer[0] = msg[0] + number[0];
MoveTo(20, y); DrawString(buffer);
}
/*
This routine is the last routine called as part of an ODOC event.
In this routine you would place code to process any structures, etc.
that you setup in the PreflightDocs routine.
NEW IN 2.0!
The itemCount parameter was the number of items that you processed.
It is passed here just in case you need it ;)
If you created a userDataHandle in the PreFlightDocs routines, this is
the place to dispose of it since the Shell will NOT do it for you!
You will probably want to remove the #pragma unusued (currently there to fool the compiler!)
*/
#pragma segment Main
pascal void PostFlightDocs ( Boolean opening, short itemCount, Handle userDataHandle ) {
#pragma unused ( opening )
#pragma unused ( itemCount )
#pragma unused ( userDataHandle )
if ( (opening) && (!gOApped) )
gDone = true; // close everything up!
HUnlock(InBuffer);
HUnlock(OutBuffer1);
HUnlock(OutBuffer2);
#if DO_TIMING
Microseconds(&total_end_time);
WideSubtract((wide*)&total_end_time, (wide*)&total_start_time);
EraseRect(&StatusWindow->portRect);
ShowWindowMsg( 30, "\pRead: ", read_time.hi *1000 + read_time.lo/1000);
ShowWindowMsg( 50, "\pCreate: ", create_time.hi *1000 + create_time.lo/1000);
ShowWindowMsg( 70, "\pWrite: ", write_time.hi *1000 + write_time.lo/1000);
ShowWindowMsg( 90, "\pClose: ", close_time.hi *1000 + close_time.lo/1000);
ShowWindowMsg(110, "\pTotal: ", total_end_time.hi*1000 + total_end_time.lo/1000);
while (!Button()) continue;
#endif
StUU_Exit();
/*
The reason we do not auto quit is based on a recommendation in the
Apple event Registry which specifically states that you should NOT
quit on a 'pdoc' as the Finder will send you a 'quit' when it is
ready for you to do so.
*/
}
// *********************************************************************************
// Main StUU Code body starts here
// I treat any non-printing ascii character as a line break (non-printing ascii
// characters except for CR and/or NL have no business being in UUencoded data anyway)
#define IS_LINE_BREAK(C) ((unsigned char)(C) < (unsigned char)32)
static Ptr ReadNextLine(void)
{
register Ptr theData;
register Ptr InPtrCopy = InPtr;
register Ptr InPtrLimit = InPtrCopy + 255;
// If data is all contiguous in memory use fast optimistic case
if (InPtrLimit < InEnd)
{
if (IS_LINE_BREAK(*InPtrCopy)) InPtrCopy++;
theData = InPtrCopy;
while (InPtrCopy < InPtrLimit && !IS_LINE_BREAK(*InPtrCopy)) InPtrCopy++;
if (InPtrCopy > theData) // If we read a non-blank line
{
LineLength = InPtrCopy - theData; // Set the length
*InPtrCopy++ = 0; // Terminate the line
InPtr = InPtrCopy; // Update the pointer
return(theData); // and return
}
}
// If the optimistic case fails, try the slow mechanism
InPtrLimit = InEnd;
theData = TempLineBuffer;
while (1)
{
while (InPtrCopy < InPtrLimit && theData < &TempLineBuffer[255] &&
!IS_LINE_BREAK(*InPtrCopy)) *theData++ = *InPtrCopy++;
if (InPtrCopy < InPtrLimit) // We haven't run out of data yet
{
InPtrCopy++; // Skip over the newline
// Test to see if the line is has some contents (empty lines are ignored)
if (theData > TempLineBuffer) break;
}
else // We broke out of loop because this chunk is over, so read some more
{
OSErr err;
long inOutCount = BytesRemaining;
if (inOutCount > InHandleSize) inOutCount = InHandleSize;
if (inOutCount == 0) break; // If no more to read, stop
#if DO_TIMING
Microseconds(&read_start_time);
#endif
UpdateStatusWindow(TRUE);
err = FSRead(ReadRefNum, &inOutCount, *InBuffer);
UpdateStatusWindow(FALSE);
#if DO_TIMING
Microseconds(&read_end_time);
WideSubtract((wide*)&read_end_time, (wide*)&read_start_time);
WideAdd((wide*)&read_time, (wide*)&read_end_time);
#endif
if (err) break; // If error reading, stop
InPtrCopy = *InBuffer;
InPtrLimit = InPtrCopy + inOutCount;
BytesRemaining -= inOutCount;
if (inOutCount == 0) { DebugStr("\pError: FSRead read nothing"); break; }
}
}
LineLength = theData - TempLineBuffer; // Set the length
*theData = 0; // Terminate the line
InPtr = InPtrCopy; // Update the pointers
InEnd = InPtrLimit;
return(TempLineBuffer);
}
static void GetFileType(const unsigned char *name, OSType *t, OSType *c)
{
ICMapEntry entry;
ICError ICerr = -1;
*t = *c = '????';
if (IC_mappings) ICerr = ICMapEntriesFilename(IC_instance, IC_mappings, name, &entry);
if (ICerr == noErr) { *t = entry.file_type; *c = entry.file_creator; }
}
static void SetNextName(void)
{
outputSpec.name[output_namesuffix_length ] = output_namesuffix++;
outputSpec.name[0] = output_namesuffix_length;
outputSpec.name[output_namesuffix_length-1] = '.';
if (output_namesuffix == '9'+1) output_namesuffix = 'A';
}
static pascal void CreateComplete(void)
{
#ifdef __MC68K__
ex_HParamBlockRec *pb = GetRegisterA0();
void *oldGlobals = GetGlobalsRegister();
SetGlobalsRegister(pb->GlobalsReg);
#endif
if (Async_Create_PB.io.ioParam.ioResult == dupFNErr) // Duplicate name; try again
{ SetNextName(); PBHCreateAsync(&Async_Create_PB.io); }
else FileBeingCreated = FALSE;
#ifdef __MC68K__
SetGlobalsRegister(oldGlobals);
#endif
}
#if GENERATINGCFM
static RoutineDescriptor CreateCompleteRD = BUILD_ROUTINE_DESCRIPTOR(uppIOCompletionProcInfo, CreateComplete);
#else
#define CreateCompleteRD CreateComplete
#endif
static OSErr StUU_Create(FSSpecPtr sourceFSSPtr, unsigned char *fileName)
{
OSErr err = noErr;
unsigned long the_time;
#if DO_TIMING
Microseconds(&create_start_time);
#endif
// Set up new FSSpec
outputSpec.vRefNum = sourceFSSPtr->vRefNum;
outputSpec.parID = sourceFSSPtr->parID;
CopyData((Ptr)fileName, (Ptr)outputSpec.name, 1+fileName[0]);
// Prepare the name suffix, in case we need it because of duplicate file names
output_namesuffix = '1';
output_namesuffix_length = fileName[0]+2;
if (output_namesuffix_length > 31) output_namesuffix_length = 31;
// Fire off the asynchronous file creation
FileBeingCreated = TRUE;
Async_Create_PB.io.ioParam.ioCompletion = &CreateCompleteRD;
Async_Create_PB.io.ioParam.ioResult = noErr;
Async_Create_PB.io.ioParam.ioNamePtr = outputSpec.name;
Async_Create_PB.io.ioParam.ioVRefNum = outputSpec.vRefNum;
Async_Create_PB.io.fileParam.ioDirID = outputSpec.parID;
#ifdef __MC68K__
Async_Create_PB.GlobalsReg = GetGlobalsRegister();
#endif
err = PBHCreateAsync(&Async_Create_PB.io);
if (err) FileBeingCreated = FALSE;
// Ask Internet Config to tell us the type and creator codes for this file
GetFileType(fileName, &output_type, &output_creator);
// Wait for file creation to complete
while (FileBeingCreated) continue;
if (Async_Create_PB.io.ioParam.ioResult) err = Async_Create_PB.io.ioParam.ioResult;
if (!err)
{
// Set the creator and type
GetDateTime(&the_time);
Async_Info_PB.fileParam.ioCompletion = NULL;
Async_Info_PB.fileParam.ioNamePtr = outputSpec.name;
Async_Info_PB.fileParam.ioVRefNum = outputSpec.vRefNum;
Async_Info_PB.fileParam.ioDirID = outputSpec.parID;
Async_Info_PB.fileParam.ioFlCrDat = the_time;
Async_Info_PB.fileParam.ioFlMdDat = the_time;
Async_Info_PB.fileParam.ioFlFndrInfo.fdType = output_type;
Async_Info_PB.fileParam.ioFlFndrInfo.fdCreator = output_creator;
Async_Info_PB.fileParam.ioFlFndrInfo.fdFlags = 0;
Async_Info_PB.fileParam.ioFlFndrInfo.fdLocation = zeroPoint;
Async_Info_PB.fileParam.ioFlFndrInfo.fdFldr = 0;
err = PBHSetFInfoAsync(&Async_Info_PB);
}
if (!err)
{
// Open the file
Async_Open_PB.fileParam.ioCompletion = NULL;
Async_Open_PB.fileParam.ioNamePtr = outputSpec.name;
Async_Open_PB.fileParam.ioVRefNum = outputSpec.vRefNum;
Async_Open_PB.fileParam.ioDirID = outputSpec.parID;
Async_Open_PB.ioParam.ioPermssn = fsWrPerm;
err = PBHOpenDFAsync(&Async_Open_PB);
}
#if DO_TIMING
Microseconds(&create_end_time);
WideSubtract((wide*)&create_end_time, (wide*)&create_start_time);
WideAdd((wide*)&create_time, (wide*)&create_end_time);
#endif
if (err) { FSpDelete(&outputSpec); ErrorAlert(kErrStringID, kCantCreateErr, err); }
else UpdateStatusWindowOutput(outputSpec.name);
return(err);
}
static void ReportErrorAndDelete(void)
{
GrafPtr savePort;
GetPort(&savePort);
FSpDelete(&closeSpec);
ParamText(closeSpec.name, NULL, NULL, NULL);
(void)Alert(Alert_DiskError, NULL);
SetPort(savePort);
}
static OSErr WriteChunk(void)
{
OSErr err;
#if DO_TIMING
Microseconds(&write_start_time);
#endif
while (Async_Open_PB.ioParam.ioResult == 1) continue; // Make sure open is complete
while (Async_Write_PB.ioParam.ioResult == 1) continue; // Make sure previous write is complete
// If the previous write failed, report it now
if (Async_Write_PB.ioParam.ioResult) return(Async_Write_PB.ioParam.ioResult);
// If our file opened correctly, write to it
if (Async_Write_PB.ioParam.ioResult == noErr && Async_Open_PB.ioParam.ioResult == noErr)
{
writeSpec = outputSpec;
Async_Write_PB.ioParam.ioCompletion = NULL;
Async_Write_PB.ioParam.ioResult = noErr;
Async_Write_PB.ioParam.ioRefNum = Async_Open_PB.ioParam.ioRefNum;
Async_Write_PB.ioParam.ioBuffer = *OutBuffer;
Async_Write_PB.ioParam.ioReqCount = OutPtr - *OutBuffer;
Async_Write_PB.ioParam.ioPosMode = fsAtMark;
Async_Write_PB.ioParam.ioPosOffset = 0;
err = PBWriteAsync(&Async_Write_PB);
if (err) { Async_Write_PB.ioParam.ioResult = err; err = noErr; }
if (OutBuffer == OutBuffer1) OutBuffer = OutBuffer2; else OutBuffer = OutBuffer1;
}
OutPtr = *OutBuffer;
OutEnd = *OutBuffer + OutHandleSize;
#if DO_TIMING
Microseconds(&write_end_time);
WideSubtract((wide*)&write_end_time, (wide*)&write_start_time);
WideAdd((wide*)&write_time, (wide*)&write_end_time);
#endif
return(err);
}
// Note, made the decode table unsigned instead of signed to prevent the C compiler
// from trying to sign-extend the bytes every time it ORs them into a long
static unsigned char zero_char;
static unsigned char decode_table[256];
#define DEC(c) (decode_table[(unsigned char)(c)]) /* single character decode */
static void reset_default_table(void)
{
int i;
for (i= 0; i< 32; i++) decode_table[i] = 0;
for (i=32; i< 96; i++) decode_table[i] = i - 32;
for (i=96; i<256; i++) decode_table[i] = 0;
decode_table['`'] = decode_table[' '];
decode_table['~'] = decode_table['^'];
zero_char = ' ';
}
static void read_translation_table(void)
{
unsigned char val = 0;
int i;
for (i=0; i<256; i++) decode_table[i] = 0;
while (CurrentLine[0])
{
unsigned char *p;
CurrentLine = ReadNextLine();
p = (unsigned char *)CurrentLine;
while (p[LineLength - 1] == ' ') p[--LineLength] = 0;
if (val == 0) zero_char = *p;
while (*p) decode_table[*p++] = val++;
if (val >= 64) return;
}
}
/* Old Think C inline assembly chunk
#if THINK_C
asm {
clr.l d0
move.b (p)+, d0 ; read first byte
move.b (decoder,d0), val ; move translated value into val
move.b (p)+, d0 ; read second byte
lsl.l #6, val ; shift val left six bits
or.b (decoder,d0), val ; OR translated value into val
move.b (p)+, d0 ; read third byte
lsl.l #6, val ; shift val left six bits
or.b (decoder,d0), val ; OR translated value into val
move.b (p)+, d0 ; read final byte
lsl.l #6, val ; shift val left six bits
or.b (decoder,d0), val ; OR translated value into val
}
*/
static OSErr DecodeLine(register Ptr p)
{
register Ptr out;
register unsigned char *decoder = decode_table;
register unsigned long val;
register int linelen = decoder[(unsigned char)(*p++)];
if (OutEnd - OutPtr < 256) { OSErr err = WriteChunk(); if (err) return(err); }
out = OutPtr; // Careful! Don't set this till *after* WriteChunk
while (linelen > 0)
{
val = decoder[(unsigned char)(p[0])]; val <<= 6;
val |= decoder[(unsigned char)(p[1])]; val <<= 6;
val |= decoder[(unsigned char)(p[2])]; val <<= 6;
val |= decoder[(unsigned char)(p[3])];
out[2] = val; val >>= 8;
out[1] = val; val >>= 8;
out[0] = val;
p += 4;
out += 3;
linelen -= 3;
}
// Because we output blocks of three, we may overshoot the correct end of line
// In this case linelen will be negative and we backtrack OutPtr by that amount.
OutPtr = out + linelen;
return(noErr);
}
#define MAX_STASHED_LINES 8
#define MAX_STASHED_LINE_LENGTH 88
typedef struct { char c[MAX_STASHED_LINE_LENGTH]; } TextLine;
#define PERFECT_LINE (CurrentLine[0] == 'M' && LineLength >= 61 && LineLength <= 63)
// sourceFSSPtr is the uuencoded file being decoded
// hp points to the "begin 644 filename.foo" header line
static OSErr DecodeFile(FSSpecPtr sourceFSSPtr, Ptr hp)
{
OSErr err, closeErr;
TextLine LastPerfectLine;
TextLine LineStash[MAX_STASHED_LINES];
int num_stashed_lines = 0;
Boolean GotPerfectLine = FALSE;
int i;
Ptr filemode;
unsigned char filename[32];
filename[0] = 0;
hp += 6; // Skip the "begin "
while (*hp == ' ') hp++; // Skip the spaces
filemode = hp; // The Unix file permissions
while (*hp && *hp != ' ') hp++; // Read the permission portion
if (*hp) *hp++ = 0; // Terminate the permission string
while (*hp == ' ') hp++; // Skip the spaces
while (*hp && *hp != ' ' && filename[0] < sizeof(filename) - 1)
filename[++filename[0]] = (unsigned char) *hp++;
CurrentLine = ReadNextLine(); // Read the first line of encoded data
if (!filename[0]) { ErrorAlert(kErrStringID, kNoFileNameErr, 0); return(noErr); }
// Create output file in the same directory
err = StUU_Create(sourceFSSPtr, filename);
if (err) return(err);
// The file is opened asynchronously, so the open proceeds while we start decoding
OutPtr = *OutBuffer; // Set up output pointers
OutEnd = *OutBuffer + OutHandleSize;
while (CurrentLine[0] && !err)
{
// "Good Loop": Read as many good lines as possible
while (PERFECT_LINE && !err)
{ err = DecodeLine(CurrentLine); CurrentLine = ReadNextLine(); }
GotPerfectLine = FALSE;
// "Bad Loop": Skip past the bad or questionable lines
while (CurrentLine[0] && !err)
{
int origlen = DEC(CurrentLine[0]); // Original length
int codedlength = 1 + (origlen * 4 + 2) / 3; // UUencoded length
if (CurrentLine[0] == 'e' && // check for "end" marker
CurrentLine[1] == 'n' &&
CurrentLine[2] == 'd') goto end_of_file;
if (CurrentLine[0] == 'b' && // check for another "begin" marker
CurrentLine[1] == 'e' &&
CurrentLine[2] == 'g' &&
CurrentLine[3] == 'i' &&
CurrentLine[4] == 'n' &&
CurrentLine[5] == ' ') goto end_of_file;
if (CurrentLine[0] == 't' && // check for another "table" marker
CurrentLine[1] == 'a' &&
CurrentLine[2] == 'b' &&
CurrentLine[3] == 'l' &&
CurrentLine[4] == 'e') goto end_of_file;
// If line too short, restore truncated trailing spaces
if (LineLength < codedlength)
{
// Mustn't try to expand line in place!
if (CurrentLine != TempLineBuffer)
{
CopyData(CurrentLine, TempLineBuffer, LineLength+1);
CurrentLine = TempLineBuffer;
}
while (LineLength < codedlength) CurrentLine[LineLength++] = zero_char;
CurrentLine[LineLength] = 0; // Terminate the line
}
// If we get one perfect line stash it, and set the flag.
// If we get a second perfect line, assume we are back in body text,
// and go back to the good loop
if (PERFECT_LINE)
{
if (GotPerfectLine) { err = DecodeLine(LastPerfectLine.c); break; }
num_stashed_lines = 0;
GotPerfectLine = TRUE;
CopyData(CurrentLine, LastPerfectLine.c, LineLength);
}
else
{
// Stash runt lines in case they turn out to be
// the last few lines before the "end" marker
GotPerfectLine = FALSE;
if (LineLength <= MAX_STASHED_LINE_LENGTH &&
num_stashed_lines < MAX_STASHED_LINES &&
origlen > 0 && origlen <= 45 && LineLength <= codedlength + 3)
CopyData(CurrentLine, LineStash[num_stashed_lines++].c, LineLength);
}
CurrentLine = ReadNextLine(); // Read the next line
}
}
end_of_file:
// Decode any stashed lines
if (!err && GotPerfectLine) err = DecodeLine(LastPerfectLine.c);
for (i=0; !err && i<num_stashed_lines; i++) err = DecodeLine(LineStash[i].c);
// Make sure open is complete
while (Async_Open_PB.ioParam.ioResult == 1) continue;
// If file failed to be opened, give the error message
if (Async_Open_PB.ioParam.ioResult)
{
FSpDelete(&outputSpec);
ErrorAlert(kErrStringID, kCantCreateErr, Async_Open_PB.ioParam.ioResult);
return(Async_Open_PB.ioParam.ioResult);
}
// Write the data out to disk
if (!err && OutPtr > *OutBuffer) err = WriteChunk();
// The file is written asynchronously, so the write proceeds while we continue
// decoding the next file
// And close the file
#if DO_TIMING
Microseconds(&close_start_time);
#endif
while (Async_Close_PB.ioParam.ioResult == 1) continue; // Make sure previous close is complete
if (Async_Close_PB.ioParam.ioResult) ReportErrorAndDelete();
// If the previous close failed, report it now
closeSpec = outputSpec;
Async_Close_PB.ioParam.ioCompletion = NULL;
Async_Close_PB.ioParam.ioResult = noErr;
Async_Close_PB.ioParam.ioRefNum = Async_Open_PB.ioParam.ioRefNum;
closeErr = PBCloseAsync(&Async_Close_PB);
if (closeErr) Async_Close_PB.ioParam.ioResult = closeErr;
// The file is closed asynchronously, so the close proceeds while we continue
// decoding the next file
#if DO_TIMING
Microseconds(&close_end_time);
WideSubtract((wide*)&close_end_time, (wide*)&close_start_time);
WideAdd((wide*)&close_time, (wide*)&close_end_time);
#endif
if (err) ReportErrorAndDelete();
return(err);
}
// *********************************************************************************
/*
This routine gets called for each item (which could be either a file or a folder)
that the caller wants dropped. The determining factor is the definition of the
qWalkFolder compiler directive. Either way, the item in question should be
processed as a single item and not "dissected" into component units (like subfiles
of a folder!)
*/
static OSErr ProcessItem(FSSpecPtr myFSSPtr)
{
OSErr err = noErr;
err = FSpOpenDF(myFSSPtr, fsRdPerm, &ReadRefNum);
if (err) return(err);
err = GetEOF(ReadRefNum, &BytesRemaining);
if (err) { FSClose(ReadRefNum); return(err); }
InPtr = InEnd = NULL;
UpdateStatusWindowInput(myFSSPtr->name);
reset_default_table();
CurrentLine = ReadNextLine(); // Load first line of file into CurrentLine
while (err == noErr && CurrentLine[0])
{
if (CurrentLine[0] == 'e' && // check for "end" marker
CurrentLine[1] == 'n' &&
CurrentLine[2] == 'd') reset_default_table();
if (CurrentLine[0] == 't' &&
CurrentLine[1] == 'a' &&
CurrentLine[2] == 'b' &&
CurrentLine[3] == 'l' &&
CurrentLine[4] == 'e') read_translation_table();
if (CurrentLine[0] == 'b' &&
CurrentLine[1] == 'e' &&
CurrentLine[2] == 'g' &&
CurrentLine[3] == 'i' &&
CurrentLine[4] == 'n' &&
CurrentLine[5] == ' ')
{
err = DecodeFile(myFSSPtr, CurrentLine);
if (IsError(Async_Write_PB.ioParam.ioResult)) break;
reset_default_table();
}
else CurrentLine = ReadNextLine(); // Read next line
}
FSClose(ReadRefNum);
while (Async_Close_PB.ioParam.ioResult == 1) continue; // Make sure final output close is complete
// If we exited because of a write error, report it and delete the file
if (Async_Write_PB.ioParam.ioResult)
{
ErrorAlert(kErrStringID, kDiskFullErr, Async_Write_PB.ioParam.ioResult);
FSpDelete(&writeSpec);
ParamText(writeSpec.name, NULL, NULL, NULL);
(void)Alert(Alert_DiskError, NULL);
}
if (Async_Close_PB.ioParam.ioResult) ReportErrorAndDelete();
return(err);
}
/*
This routine gets called for any folder (or disk) that the caller wants
processed as a set of component items, instead of as a single entity.
The determining factor is the definition of the qWalkFolder compiler directive.
*/
static OSErr ProcessFolder(FSSpecPtr myFSSPtr)
{
OSErr err = noErr;
short index, oldIndex, localIndex;
FSSpec localFSSpec, curFSSpec;
CInfoPBRec cipb;
Str255 fName, vFName;
long dirID, origDirID;
Boolean foundPosition;
// copy the source locally to avoid recursion problems
BlockMoveData(myFSSPtr, &localFSSpec, sizeof(FSSpec));
// get the dirID for THIS folder, not it's parent!
BlockMoveData(localFSSpec.name, fName, 32);
cipb.hFileInfo.ioCompletion = 0L;
cipb.hFileInfo.ioNamePtr = fName;
cipb.hFileInfo.ioVRefNum = localFSSpec.vRefNum;
cipb.hFileInfo.ioFDirIndex = 0; // use the dir & vRefNum;
cipb.hFileInfo.ioDirID = localFSSpec.parID;
err = PBGetCatInfoSync(&cipb);
if (!err) {
origDirID = cipb.dirInfo.ioDrDirID; // copy the sucker
index = 1;
// index through all contents of this folder
while (err == noErr) {
dirID = origDirID;
localIndex = index;
fName [0] = 0;
cipb.hFileInfo.ioCompletion = 0L;
cipb.hFileInfo.ioNamePtr = fName;
cipb.hFileInfo.ioVRefNum = localFSSpec.vRefNum;
cipb.hFileInfo.ioFDirIndex = localIndex; // use a real index
cipb.hFileInfo.ioDirID = dirID;
err = PBGetCatInfoSync(&cipb);
if (!err) {
BlockMoveData(fName, curFSSpec.name, 32);
curFSSpec.vRefNum = cipb.hFileInfo.ioVRefNum;
curFSSpec.parID = dirID;
/*
Check to see if this entry is a folder.
*/
if (cipb.hFileInfo.ioFlAttrib & ioDirMask) {
err = ProcessFolder(&curFSSpec);
} else
err = ProcessItem(&curFSSpec);
/* If we've had an error, get out! */
if (err) break;
// dirID = origDirID;
localIndex = index;
/*
Now take into account new files being created
in the current directory & messing up our index.
See Dev.CD Vol. XI:Tools & Apps (Moof!):Misc Utilities:
Disinfectant & Source 2.5.1:Sample:Notes:Scan Alg
*/
vFName [0] = 0;
cipb.hFileInfo.ioCompletion = 0L;
cipb.hFileInfo.ioNamePtr = vFName;
cipb.hFileInfo.ioVRefNum = localFSSpec.vRefNum;
cipb.hFileInfo.ioFDirIndex = localIndex; // use a real index
cipb.hFileInfo.ioDirID = dirID;
err = PBGetCatInfoSync(&cipb);
oldIndex = index;
if (!err) {
/* If they're equal - same place, go to next */
if (EqualString (vFName, fName, false, false))
index++;
}
/* If we didn't advance, then perhaps a file was created or deleted */
if (oldIndex == index) {
oldIndex = index; /* save off the old */
index = 0; /* and start at the beginning */
err = noErr;
vFName [0] = 0;
foundPosition = false;
while (!foundPosition) {
index++;
vFName [0] = 0;
cipb.hFileInfo.ioCompletion = 0L;
cipb.hFileInfo.ioNamePtr = vFName;
cipb.hFileInfo.ioVRefNum = localFSSpec.vRefNum;
cipb.hFileInfo.ioFDirIndex = index; /* now use a real index */
cipb.hFileInfo.ioDirID = dirID;
err = PBGetCatInfoSync(&cipb);
if (err == fnfErr) { // we've just been deleted
index = oldIndex;
foundPosition = true;
err = noErr; // have to remember to reset this!
}
/* found same file & same index position */
/* so try the next item */
if ((!foundPosition) && EqualString(fName, vFName, false, false)) {
index++;
foundPosition = true;
}
}
}
}
}
}
return(err);
}
/*
This routine is called when the user chooses "Select File…" from the
File Menu.
Currently it simply calls the new StandardGetFile routine to have the
user select a single file (any type, numTypes = -1) and then calls the
SendODOCToSelf routine in order to process it.
The reason we send an odoc to ourselves is two fold: 1) it keeps the code
cleaner as all file openings go through the same process, and 2) if events
are ever recordable, the right things happen (this is called Factoring!)
Modification of this routine to only select certain types of files, selection
of multiple files, and/or handling of folder & disk selection is left
as an exercise to the reader.
*/
pascal void SelectFile(void)
{
StandardFileReply stdReply;
SFTypeList theTypeList;
StandardGetFile(NULL, -1, theTypeList, &stdReply);
if (stdReply.sfGood) // user did not cancel
SendODOCToSelf(&stdReply.sfFile); // so send me an event!
}
/*
This routine is called during the program's initialization and gives you
a chance to allocate or initialize any of your own globals that your
dropbox needs.
You return a boolean value which determines if you were successful.
Returning false will cause DropShell to exit immediately.
*/
pascal Boolean InitUserGlobals(void)
{
return(true); // nothing to do, it we must be successful!
}
/*
This routine is called during the program's cleanup and gives you
a chance to deallocate any of your own globals that you allocated
in the above routine.
*/
pascal void DisposeUserGlobals(void)
{
// nothing to do for our sample dropbox
}